package gl.drawer

import gl.window
import org.lwjgl.glfw.GLFW
import org.lwjgl.opengl.GL44C.*

// todo 有关换乘区测试，包括模板测试，深度测试等，后期补充
class Stencil: Drawer() {

    private var program = 0
    private var buffer = 0
    private var vertex = 0

    private var lastWidth: Int = 0
    private var lastHeight: Int = 0
    private var type = 0

    override fun initialize() {
        buffer = glGenBuffers()
        glBindBuffer(GL_ARRAY_BUFFER, buffer)
        glBufferStorage(GL_ARRAY_BUFFER, 64L, 0)
        glBufferSubData(GL_ARRAY_BUFFER, 0L, floatArrayOf(
            -1f,  0f,
             1f,  0f,
             1f, .5f,
            -1f, .5f,
            .5f, -1f,
             0f, -1f,
             0f,  1f,
            .5f,  1f))

        val vs = glCreateShader(GL_VERTEX_SHADER)
        glShaderSource(vs, vertexSourceStencil)
        glCompileShader(vs)
        val fs = glCreateShader(GL_FRAGMENT_SHADER)
        glShaderSource(fs, fragmentSourceStencil)
        glCompileShader(fs)
        program = glCreateProgram()
        glAttachShader(program, vs)
        glAttachShader(program, fs)
        glLinkProgram(program)
        glDetachShader(program, vs)
        glDetachShader(program, fs)
        glDeleteBuffers(intArrayOf(vs, fs))
        glUseProgram(program)

        vertex = glGenVertexArrays()
        glBindVertexArray(vertex)
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0L)
        glDisableVertexAttribArray(1)
        glVertexAttrib4f(1, 1f, 0f, 0f, 0.5f)
    }

    override fun resume() {
        super.resume()

        glUseProgram(program)
        glBindBuffer(GL_ARRAY_BUFFER, buffer)
        glBindVertexArray(vertex)
        glEnableVertexAttribArray(0)
        glDisableVertexAttribArray(1)

        // stencils
        if (type == 0) {
            glDisable(GL_STENCIL_TEST)
        } else {
            glEnable(GL_STENCIL_TEST)
            glStencilMask(0xFF)
            glClearStencil(0)
        }

        glEnable(GL_BLEND)
        glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA)
    }

    override fun update(): Boolean {
        super.update()
        glClear(GL_STENCIL_BUFFER_BIT) // 模板缓冲区清空为 0
        when (type) {
            1 -> {
                window.title = "$name-Difference"
                // mask area
                // 不向颜色缓冲区绘制
                glColorMask(false, false, false, false)
                // 设置模板缓冲区总是通过
                glStencilFunc(GL_ALWAYS, 0, 0xFF)
                // 设置仅当深度测试和模板测试都通过时，深度缓冲区值 + 1，其他情况维持原值 (0)
                glStencilOp(GL_KEEP, GL_KEEP, GL_INCR)
                // 横向绘制区域模板为 0000 0001, 其余均为 0x00
                glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
                // 纵绘制区域模板为 0000 0001, 与横向重合部位为 0000 0010, 其余均为 0x00
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
                // main area
                // 向颜色缓冲区绘制
                glColorMask(true, true, true, true)
                // 设置仅当模板缓冲区值与 0000 0001 做按位与后为 0000 0001 时测试通过
                glStencilFunc(GL_EQUAL, 0x01, 0x01) // v & 0b0000_0001 == 0b0000_0001
                // 绘制，但仅保留模板测试通过的片元
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
            }
            2 -> {
                window.title = "$name-Xor"
                // mask
                glColorMask(false, false, false, false)
                glStencilOp(GL_INCR, GL_INCR, GL_INCR)
                glStencilFunc(GL_ALWAYS, 0, 0xFF)
                glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
                // main area
                glColorMask(true, true, true, true)
                glStencilFunc(GL_EQUAL, 0x01, 0x01)
                glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO)
                glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
            }
            3 -> {
                window.title = "$name-Intersect"
                // mask
                glColorMask(false, false, false, false)
                glStencilOp(GL_INCR, GL_INCR, GL_INCR)
                glStencilFunc(GL_ALWAYS, 0, 0xFF)
                glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
                // main area
                glColorMask(true, true, true, true)
                glStencilFunc(GL_EQUAL, 0x02, 0x03)
                glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO)
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
            }
            4 -> {
                window.title = "$name-Union"
                glColorMask(true, true, true, true)
                glStencilOp(GL_KEEP, GL_KEEP, GL_INCR)
                glStencilFunc(GL_EQUAL, 0x00, 0x01)
                glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
            }
            else -> {
                window.title = "$name-Source"
                glColorMask(true, true, true, true)
                glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
                glDrawArrays(GL_TRIANGLE_FAN, 4, 4)
            }
        }
        return false
    }

    override fun stop() {
        glBindVertexArray(0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glUseProgram(0)
        glDisable(GL_BLEND)
        glDisable(GL_STENCIL_TEST)
        glColorMask(true, true, true, true)
    }

    override fun terminal() {
        lastWidth = 0
        lastHeight = 0
        glDeleteBuffers(buffer)
        glDeleteVertexArrays(vertex)
        glDeleteProgram(program)
    }

    override fun handleKey(keyCode: Int, action: Int) {
        super.handleKey(keyCode, action)
        if (action == GLFW.GLFW_PRESS) {
            when (keyCode) {
                GLFW.GLFW_KEY_KP_0 -> type = 0
                GLFW.GLFW_KEY_KP_1 -> type = 1
                GLFW.GLFW_KEY_KP_2 -> type = 2
                GLFW.GLFW_KEY_KP_3 -> type = 3
                GLFW.GLFW_KEY_KP_4 -> type = 4
            }
            if (type == 0) {
                glDisable(GL_STENCIL_TEST)
            } else {
                glEnable(GL_STENCIL_TEST)
            }
        }
    }
}

val vertexSourceStencil = """
#version 400 core

layout(location=0) in vec4 vPosition;
layout(location=1) in vec4 vColor;

out vec4 color;

void main() {
    gl_Position = vPosition;
    color = vColor;
}
""".trimIndent()

val fragmentSourceStencil = """
#version 400 core

in vec4 color;

out vec4 fColor;

void main() {
    fColor = color;
}
""".trimIndent()